/*
 *   Copyright 2021 zzh
 *
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 *
 */

package com.aduib.boot.common.util;

/**
 * @author ZZH
 * @version 1.0.0
 */
import java.nio.ByteBuffer;

public class ModbusUtils {
  private static final byte ASCII_START = ':';
  private static final byte[] ASCII_END = {'\r', '\n'};
  private static final short[] lookupCRCHi = {
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
    0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
    0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
    0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
    0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
    0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
    0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
    0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
    0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40
  };
  private static final short[] lookupCRCLo = {
    0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06, 0x07, 0xC7, 0x05, 0xC5, 0xC4, 0x04,
    0xCC, 0x0C, 0x0D, 0xCD, 0x0F, 0xCF, 0xCE, 0x0E, 0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09, 0x08, 0xC8,
    0xD8, 0x18, 0x19, 0xD9, 0x1B, 0xDB, 0xDA, 0x1A, 0x1E, 0xDE, 0xDF, 0x1F, 0xDD, 0x1D, 0x1C, 0xDC,
    0x14, 0xD4, 0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3, 0x11, 0xD1, 0xD0, 0x10,
    0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3, 0xF2, 0x32, 0x36, 0xF6, 0xF7, 0x37, 0xF5, 0x35, 0x34, 0xF4,
    0x3C, 0xFC, 0xFD, 0x3D, 0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A, 0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38,
    0x28, 0xE8, 0xE9, 0x29, 0xEB, 0x2B, 0x2A, 0xEA, 0xEE, 0x2E, 0x2F, 0xEF, 0x2D, 0xED, 0xEC, 0x2C,
    0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26, 0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0,
    0xA0, 0x60, 0x61, 0xA1, 0x63, 0xA3, 0xA2, 0x62, 0x66, 0xA6, 0xA7, 0x67, 0xA5, 0x65, 0x64, 0xA4,
    0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F, 0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB, 0x69, 0xA9, 0xA8, 0x68,
    0x78, 0xB8, 0xB9, 0x79, 0xBB, 0x7B, 0x7A, 0xBA, 0xBE, 0x7E, 0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C,
    0xB4, 0x74, 0x75, 0xB5, 0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71, 0x70, 0xB0,
    0x50, 0x90, 0x91, 0x51, 0x93, 0x53, 0x52, 0x92, 0x96, 0x56, 0x57, 0x97, 0x55, 0x95, 0x94, 0x54,
    0x9C, 0x5C, 0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E, 0x5A, 0x9A, 0x9B, 0x5B, 0x99, 0x59, 0x58, 0x98,
    0x88, 0x48, 0x49, 0x89, 0x4B, 0x8B, 0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C,
    0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42, 0x43, 0x83, 0x41, 0x81, 0x80, 0x40
  };
  private static byte[] lookupAscii = {
    0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46,
    0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x34, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x35, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x37, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x39, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x42, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x43, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x45, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x46, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  };
  private static byte[] lookupUnascii = {
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  };

  /** 将int转换成byte数组，低位在前，高位在后 改变高低位顺序只需调换数组序号 */
  public static byte[] intToBytes(int value) {
    byte[] src = new byte[2];
    src[1] = (byte) ((value >> 8) & 0xFF);
    src[0] = (byte) (value & 0xFF);
    return src;
  }

  /**
   * 获取源数据和验证码的组合byte数组
   *
   * @param str 可变长度的十六进制字符串
   * @return
   */
  public static byte[] getCRC2(String str) {
    byte[] data = hexString2Bytes(str);
    byte[] bb = getCRC2(data);
    byte[] cc = new byte[data.length + bb.length];
    System.arraycopy(data, 0, cc, 0, data.length);
    System.arraycopy(bb, 0, cc, data.length, bb.length);
    return cc;
  }

  /**
   * 获取16进制字符串
   *
   * @param payload
   * @return
   */
  public static String getHexString(byte[] payload) {
    StringBuilder sb = new StringBuilder();
    String sTemp;
    for (int i = 0; i < payload.length; i++) {
      sTemp = Integer.toHexString(0xFF & payload[i]);
      if (sTemp.length() < 2) {
        sb.append(0);
      }
      sb.append(sTemp.toUpperCase());
    }
    return sb.toString();
  }

  /**
   * 这是常用的方法，如某些硬件16进制字符串转十进制字节数组的通信指令就是提供的16进制字符串，发送时需要转为字节数组再进行发送
   *
   * @param strSource 16进制字符串，如 "455A432F5600"，每两位对应字节数组中的一个10进制元素 默认会去除参数字符串中的空格，所以参数 "45 5A 43 2F
   *     56 00" 也是可以的
   * @return 十进制字节数组, 如 [69, 90, 67, 47, 86, 0]
   */
  public static byte[] hexString2Bytes(String strSource) {
    if (strSource == null || "".equals(strSource.trim())) {
      System.out.println("hexString2Bytes 参数为空，放弃转换.");
      return null;
    }
    if (strSource.contains(" ")) {
      strSource = strSource.replace(" ", "");
    }
    int l = strSource.length() / 2;
    byte[] ret = new byte[l];
    for (int i = 0; i < l; i++) {
      ret[i] = Integer.valueOf(strSource.substring(i * 2, i * 2 + 2), 16).byteValue();
    }
    return ret;
  }

  @Deprecated
  public static byte[] getHexBytes(String str) {
    byte[] data = new byte[str.length()];
    String[] chars = str.split("");
    for (int i = 0; i < chars.length; i++) {
      int x = Integer.parseInt(chars[i], 16);
      byte n = (byte) x;
      data[i] = n;
      // byte[] buffer = new byte[data.length + 1];
      // byte[] aa = {n};
      // System.arraycopy(data, 0, buffer, 0, data.length);
      // System.arraycopy(aa, 0, buffer, data.length, aa.length);
      // data = buffer;
    }
    return data;
  }

  public static byte[] getCRC2(byte[] bytes) {
    //        ModBus 通信协议的 CRC ( 冗余循环校验码含2个字节, 即 16 位二进制数。
    //        CRC 码由发送设备计算, 放置于所发送信息帧的尾部。
    //        接收信息设备再重新计算所接收信息 (除 CRC 之外的部分）的 CRC,
    //        比较计算得到的 CRC 是否与接收到CRC相符, 如果两者不相符, 则认为数据出错。
    //
    //        1) 预置 1 个 16 位的寄存器为十六进制FFFF(即全为 1) , 称此寄存器为 CRC寄存器。
    //        2) 把第一个 8 位二进制数据 (通信信息帧的第一个字节) 与 16 位的 CRC寄存器的低 8 位相异或, 把结果放于 CRC寄存器。
    //        3) 把 CRC 寄存器的内容右移一位( 朝低位)用 0 填补最高位, 并检查右移后的移出位。
    //        4) 如果移出位为 0, 重复第 3 步 ( 再次右移一位); 如果移出位为 1, CRC 寄存器与多项式A001 ( 1010 0000 0000 0001) 进行异或。
    //        5) 重复步骤 3 和步骤 4, 直到右移 8 次,这样整个8位数据全部进行了处理。
    //        6) 重复步骤 2 到步骤 5, 进行通信信息帧下一个字节的处理。
    //        7) 将该通信信息帧所有字节按上述步骤计算完成后,得到的16位CRC寄存器的高、低字节进行交换。
    //        8) 最后得到的 CRC寄存器内容即为 CRC码。

    int CRC = 0x0000ffff;
    int POLYNOMIAL = 0x0000a001;

    int i, j;
    for (i = 0; i < bytes.length; i++) {
      CRC ^= (int) bytes[i];
      for (j = 0; j < 8; j++) {
        if ((CRC & 0x00000001) == 1) {
          CRC >>= 1;
          CRC ^= POLYNOMIAL;
        } else {
          CRC >>= 1;
        }
      }
    }
    // 高低位转换，看情况使用（譬如本人这次对led彩屏的通讯开发就规定校验码高位在前低位在后，也就不需要转换高低位)
    // CRC = ( (CRC & 0x0000FF00) >> 8) | ( (CRC & 0x000000FF ) << 8);
    // System.out.println(Integer.toHexString(CRC));
    return intToBytes(CRC);
  }

  @Deprecated
  public static byte[] toAsciiBytes(String s) throws Exception {
    return s.getBytes("ASCII");
  }

  /*
   * 输入字符串w 返回lrc校验部分
   * @param  w=需要传输的信息  ID是地址
   * @return finaldata
   */
  @Deprecated
  public static byte Lrc(byte[] data) {
    int tmp = 0;
    for (int i = 0; i < data.length; i++) {
      tmp = tmp + (byte) data[i];
    }
    tmp = ~tmp;
    tmp = (tmp & (0xff));
    tmp += 1;
    return (byte) tmp;
  }

  public static byte[] readAsciiByAdu(byte[] bytes) {
    return readAscii(bytes, 1, bytes.length - 2);
  }

  public static byte[] readAscii(byte[] bytes, int start, int end) {
    if (end > bytes.length) {
      end = bytes.length;
    }
    ByteBuffer buffer = ByteBuffer.allocate((end - start) / 2);
    for (int i = start; i < end; i = i + 2) {
      byte b = (byte) ((lookupUnascii[bytes[i]] << 4) | lookupUnascii[bytes[i + 1]]);
      buffer.put(b);
    }
    return buffer.array();
  }

  public static void writeAsciiStart(ByteBuffer buffer) {
    buffer.put(ASCII_START);
  }

  public static void writeAsciiEnd(ByteBuffer buffer) {
    buffer.put(ASCII_END);
  }

  //    public static void writeCRC(ByteBuffer buffer) {
  //        int end = buffer.position();
  //        int crc = ModbusUtils.calculateCRC(buffer.array(), 0, end);
  //        buffer.put(ByteUtils.getByte2(crc));
  //        buffer.put(ByteUtils.getByte1(crc));
  //    }

  public static void writeAscii(ByteBuffer buffer, byte[] bytes) {
    for (byte b : bytes) {
      writeAscii(buffer, b);
    }
  }

  public static void writeAscii(ByteBuffer buffer, byte b) {
    buffer.put(lookupAscii[b & 0xf0]);
    buffer.put(lookupAscii[b & 0x0f]);
  }

  public static void writeAsciiAndLrc(ByteBuffer buffer, byte[] bytes) {
    for (byte b : bytes) {
      writeAscii(buffer, b);
    }
    byte lrc = calculateLRC(bytes, 0, bytes.length);
    writeAscii(buffer, lrc);
  }

  public static byte calculateLRC(byte[] bytes, int start, int end) {
    if (end > bytes.length) {
      end = bytes.length;
    }
    int lrc = 0;
    for (int i = 0; i < end; i++) {
      lrc -= bytes[(i + start)];
    }
    return (byte) (lrc & 0xff);
  }

  public static int calculateCRC(byte[] queue) {
    return calculateCRC(queue, 0, queue.length);
  }

  public static int calculateCRC(byte[] queue, int start, int end) {
    if (end > queue.length) {
      end = queue.length;
    }
    int high = 0xff;
    int low = 0xff;
    int nextByte = 0;
    int uIndex;
    for (int i = start; i < end; i++) {
      nextByte = 0xFF & queue[i];
      uIndex = high ^ nextByte;
      high = low ^ lookupCRCHi[uIndex];
      low = lookupCRCLo[uIndex];
    }
    return (high << 8) | low;
  }
}
